package pl.polsl.mmm.projectcomm.objects;

import android.content.Context;

import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.FixtureDef;
import org.jbox2d.dynamics.World;

import java.util.ArrayList;

import static android.opengl.Matrix.multiplyMM;
import static pl.polsl.mmm.projectcomm.Constants.*;

import pl.polsl.mmm.projectcomm.Constants;
import pl.polsl.mmm.projectcomm.R;
import pl.polsl.mmm.projectcomm.data.vec3;
import pl.polsl.mmm.projectcomm.data.vec4;
import pl.polsl.mmm.projectcomm.objects.MD5.MD5;
import pl.polsl.mmm.projectcomm.objects.MD5.MD5Action;
import pl.polsl.mmm.projectcomm.objects.MD5.MD5Joint;
import pl.polsl.mmm.projectcomm.objects.state.BobState;
import pl.polsl.mmm.projectcomm.objects.state.MD5ActionState;
import pl.polsl.mmm.projectcomm.objects.state.MD5JointState;
import pl.polsl.mmm.projectcomm.programs.ColorShaderProgram;
import pl.polsl.mmm.projectcomm.programs.TextureLightShaderProgram;
import pl.polsl.mmm.projectcomm.programs.TextureShaderProgram;
import pl.polsl.mmm.projectcomm.util.MD5Parser;
import pl.polsl.mmm.projectcomm.util.TextureHelper;

import static android.opengl.Matrix.rotateM;
import static android.opengl.Matrix.scaleM;
import static android.opengl.Matrix.setIdentityM;
import static android.opengl.Matrix.translateM;

/**
 * Opakowany model Boba
 * Created by Maciek on 2014-12-02.
 */
public class ModelBob {
    //wektor lokacji całego modelu
    private vec3 location;
    //wektor rotacji całego modelu
    private vec4 rotation;
    //skala całego modelu
    private vec3 scale;
    private MD5 model;
    //tablica z identyfikatorami tekstur
    private int[] textures;
    //aktualna poza modelu
    private MD5Joint[] actualPose;
    //poza do ruszania rękami
    public MD5Joint[] moveHandsPose;
    //shader danego modelu
    private TextureLightShaderProgram program;
    //shader linii do debugu normali i kości
    private ColorShaderProgram colorProgram;
    //akcje które są wczyne
    private MD5Action idleAction;
    private MD5Action walkAction;
    private MD5Action jumpAction;
    //macierz modelu
    private float[] modelMatrix = new float[16];
    //stan modelu
    private Model_state modelState;

    private int gest;
    // Ciało fizyczne
    private Body box;
    private World world = null;
    private boolean physics = false;

    private float speedRate = 0;
    private vec3 touchedPoint;

    public ModelBob(Context context, TextureLightShaderProgram program, ColorShaderProgram colorProgram, World pWorld, boolean physics) {

        this.program = program;
        this.colorProgram = colorProgram;
        this.physics = physics;

        modelState = Model_state.IDLE;
        touchedPoint = new vec3(0,0,0);
        location = new vec3(0f, 10f,-15f);
        rotation = vec4.quaternionRotationX((float)Math.PI/2f);
        //zeskalowany w połowie w osi x bo ekran jest podzielony na pół
        scale    = new vec3(0.5f,1f,1f);
        model = MD5Parser.loadMesh(context, R.raw.bob);
        textures = new int[4];
        textures[0] = TextureHelper.loadTexture(context, R.drawable.bob_body);
        textures[1] = TextureHelper.loadTexture(context, R.drawable.bob_head);
        textures[2] = TextureHelper.loadTexture(context, R.drawable.bob_helmet);
        textures[3] = TextureHelper.loadTexture(context, R.drawable.bob_lamp);
        model.firstBuild(textures);
        //actualizeVertexPos = false;
        actualPose = new MD5Joint[model.getnJoint()];
        moveHandsPose = new MD5Joint[model.getnJoint()];
        for(int i=0;i<model.getnJoint();i++) {
            actualPose[i] = new MD5Joint();
            moveHandsPose[i] = new MD5Joint(model.bindPose[i]);
            actualPose[i].setParent(model.bindPose[i].getParent());
        }

        this.world = pWorld;

        // Dodanie fizyki
        if (physics && world != null) {

            BodyDef boxDef = new BodyDef();
            boxDef.position.set(this.getLocation().x, this.getLocation().y);
            boxDef.fixedRotation = true;
            boxDef.type = BodyType.DYNAMIC;
            PolygonShape boxShape = new PolygonShape();
            boxShape.setAsBox(3f, 6f);
            box = world.createBody(boxDef);
            FixtureDef boxFixture = new FixtureDef();
            boxFixture.density = 1;
            boxFixture.shape = boxShape;
            box.createFixture(boxFixture);
        }
    }

    /**Inicjalizacja struktur do debugowania normali. TYLKO DO DEBUGU!*/
    public void debugNormalsON() {
        model.alocateDebugNormalsLines();
    }
    /**Inicjalizacja struktur do debugowania kości szkieletu. TYLKO DO DEBUGU!*/
    public void debugSkeletonON() {
        model.alocateSkeletonLines();
    }

    public void basicMatrix() {
        setIdentityM(modelMatrix, 0);
        scaleM(modelMatrix, 0, scale.x, scale.y, scale.z);
        translateM(modelMatrix, 0, location.x, location.y, location.z);
        //obrót modelu o kwaternion
        rotation = vec4.quaternionRotationX((float)Math.PI/2f);
        final float[] temp = new float[16];
        multiplyMM(temp, 0, modelMatrix, 0, rotation.quaternionToRotationMatrix(), 0);
        System.arraycopy(temp, 0, modelMatrix, 0, temp.length);
    }

    public void turnMatrix(boolean direction) {
        vec4 rotationHelp;
        setIdentityM(modelMatrix, 0);
        scaleM(modelMatrix, 0, scale.x, scale.y, scale.z);
        translateM(modelMatrix, 0, location.x, location.y, location.z);
        if(direction)
            rotationHelp = vec4.quaternionRotationY(-(float)Math.PI/2f);
        else
            rotationHelp = vec4.quaternionRotationY((float)Math.PI/2f);
        rotation.multiplyVec4(vec4.quaternionRotationX((float)Math.PI/2f),rotationHelp);
        //obrót modelu o kwaternion
        final float[] temp = new float[16];
        multiplyMM(temp, 0, modelMatrix, 0, rotation.quaternionToRotationMatrix(), 0);
        System.arraycopy(temp, 0, modelMatrix, 0, temp.length);
    }

    public void createBob2Matrix() {
        setIdentityM(modelMatrix, 0);
        scaleM(modelMatrix, 0, scale.x, scale.y, scale.z);
        translateM(modelMatrix, 0, location.x, location.y, location.z);
        //obrót modelu o kwaternion
        final float[] temp = new float[16];
        multiplyMM(temp, 0, modelMatrix, 0, rotation.quaternionToRotationMatrix(), 0);
        System.arraycopy(temp, 0, modelMatrix, 0, temp.length);
    }

    public void animateBob2() {
        switch(modelState) {
            case WALK:
                walkAction.actionPlay(MD5_METHOD_LERP, true);
                createBob2Matrix();
                if (speedRate > 0f) {
                    model.addPose(actualPose, idleAction, walkAction, speedRate);

                } else if (speedRate < 0f) {
                    model.addPose(actualPose, idleAction, walkAction, -speedRate);
                }
                model.drawAction(1.0f / 60.0f);
                break;
            case IDLE:
                if(idleAction.getState() == ACTION_STATE.STOP)
                    idleAction.actionPlay(MD5_METHOD_LERP, true);
                model.drawAction(1.0f / 60.0f);
                createBob2Matrix();
                break;
            case PUPPET_STATE:
                idleAction.actionStop();
                createBob2Matrix();
                if(gest == 1)
                    animateHand(touchedPoint, true);
                else if (gest == 2) {
                    animateHand(new vec3(-touchedPoint.x,touchedPoint.y,touchedPoint.z), false);
                    animateHand(touchedPoint, true);
                }

                break;
            case JUMP:
                //zatrzymanie poprzednich animacji
                idleAction.actionStop();
                walkAction.actionStop();
                //uruchomienie animacji skoku
                jumpAction.actionPlay(MD5_METHOD_LERP, false);
                createBob2Matrix();
                model.drawAction(1.0f / 60.0f);
                break;
            default:
                break;
        }
    }


    public void animate(float speedRate) {

        this.speedRate = speedRate;

        switch(modelState) {
            case WALK:
                if (speedRate > 0f) {
                    walkAction.actionPlay(MD5_METHOD_LERP, true);
                    model.addPose(actualPose, idleAction, walkAction, speedRate);
                    turnMatrix(true);
                } else if (speedRate < 0f) {
                    speedRate = -speedRate;
                    walkAction.actionPlay(MD5_METHOD_LERP, true);
                    model.addPose(actualPose, idleAction, walkAction, speedRate);
                    turnMatrix(false);
                }
                model.drawAction(1.0f / 60.0f);
            break;
            case IDLE:
                if(idleAction.getState() == ACTION_STATE.STOP)
                    idleAction.actionPlay(MD5_METHOD_LERP, true);
                model.drawAction(1.0f / 60.0f);
                basicMatrix();
                break;
            case PUPPET_STATE:
                idleAction.actionStop();
                break;
            case JUMP:
                //zatrzymanie poprzednich animacji
                idleAction.actionStop();
                walkAction.actionStop();
                //uruchomienie animacji skoku
                jumpAction.actionPlay(MD5_METHOD_LERP, false);

                model.drawAction(1.0f / 60.0f);
                if (speedRate > 0f){
                    turnMatrix(true);
                } else if (speedRate < 0f){
                    turnMatrix(false);
                } else {
                    basicMatrix();
                }
                break;
            default:
                break;
        }
    }

    public void loadActions(Context context) {
        //MD5Action idleAction, walkAction,jumpAction;
        MD5Parser.loadAction(model,"idle",context,R.raw.bob_idle);
        MD5Parser.loadAction(model,"walk",context,R.raw.bob_walk);
        MD5Parser.loadAction(model,"jump",context,R.raw.bob_jump);

        idleAction = model.getMD5ActionByName("idle");
        idleAction.actionPlay(MD5_METHOD_LERP, true);
        idleAction.setFps(30.0f);

        walkAction = model.getMD5ActionByName("walk");
        walkAction.setFps(30.0f);

        jumpAction = model.getMD5ActionByName("jump");
        jumpAction.setFps(60.0f);
    }

    public void drawModel() {

        // zsynchronizuj model fizyczny z wizualnym
        if (physics) {
            applyPhysics();
        }

        switch (modelState) {
            case WALK:
                turnOnLamp();
                model.actualizeBuild(actualPose);
                break;
            case IDLE:
                turnOnLamp();
                model.actualizeBuild(idleAction.getPose());
                break;
            case JUMP:
                turnOnLamp();
                model.actualizeBuild(jumpAction.getPose());
                break;
            case PUPPET_STATE:
                model.actualizeBuild(moveHandsPose);
                turnOffLamp();
                break;
            default:
                break;
        }
        model.draw(program);
    }
    /** rysowanie normali. TYLKO DO DEBUGU!!*/
    public void drawNormals() {
        model.drawNormalLines(colorProgram);
    }
    /** rysowanie kości. TYLKO DO DEBUGU!!*/
    public void drawSkeleton() {
        switch (modelState) {
            case WALK:
                model.drawSkeletonLines(actualPose, colorProgram);
                break;
            case IDLE:
                model.drawSkeletonLines(idleAction.getPose(), colorProgram);
                break;
            case JUMP:
                model.drawSkeletonLines(jumpAction.getPose(), colorProgram);
                break;
            default:
                model.drawSkeletonLines(moveHandsPose, colorProgram);
                break;
        }
    }
    public float[] getModelMatrix() {
        return modelMatrix;
    }

    public BobState getBobState() {
        BobState bobState = new BobState();

        bobState.location = this.location;
        bobState.rotation = this.rotation;
        bobState.md5action = new ArrayList<MD5ActionState>();
        bobState.modelState = this.modelState;
        bobState.speedRate = this.speedRate;
        bobState.touchedPoint = this.touchedPoint;
        bobState.gesture = this.gest;
//
//        MD5JointState joint;
//
//        for (MD5Joint pose: this.moveHandsPose) {
//            joint = new MD5JointState();
//            joint.rotation = new vec4(pose.getRotation());
//            joint.location = new vec3(pose.getLocation());
//            bobState.moveHandsPose.add(joint);
//        }

        for (MD5Action md5action : this.model.md5action) {

            MD5ActionState actionState = new MD5ActionState();

            actionState.curr_frame = md5action.getCurr_frame();
           // actionState.next_frame = md5action.getNext_frame();
            actionState.state = md5action.getState();

            bobState.md5action.add(actionState);
        }

        return bobState;
    }

    public void animateHand(vec3 point, boolean whichHand) {
        //R/L UPPERARM 8/16
        //R/L FOREARM 9/17
        //R/L WRIST 10/18
        //R/L THUMB 11/19
        //R/L THM_END	12/20
        //R/L FINGERS 13/21
        //R/L FINGERSTIP 14/22
        vec3[] newPositions = new vec3[6];
        if(whichHand) {
            for (int i = 17; i < 23; i++) {
                //przesuwamy wektor punktu o wektor lokacji kości parenta
                newPositions[i - 17] = vec3.calculatePosition(moveHandsPose[16].getLocation(), moveHandsPose[i].getLocation(), point);
            }
            for (int i = 17; i < 23; i++) {
                //wyznaczamy orientacją kości wzglądem osi z
               /* vec4 newRotation = new vec4();
                newRotation.multiplyVec4(
                        vec4.get_rotation_between(moveHandsPose[i].getLocation(),newPositions[i-17]),
                        moveHandsPose[i-1].getRotation());

                moveHandsPose[i-1].setRotation(newRotation);*/
                //ustawiamy wcześniej wyliczone pozycje
                moveHandsPose[i].setLocation(newPositions[i - 17]);
            }
        } else {
            for (int i = 9; i < 15; i++) {
                //przesuwamy wektor punktu o wektor lokacji kości parenta
                newPositions[i - 9] = vec3.calculatePosition(moveHandsPose[8].getLocation(), moveHandsPose[i].getLocation(), point);
            }
            for (int i = 9; i < 15; i++) {
                //przesuwamy wektor punktu o wektor lokacji kości parenta
                moveHandsPose[i].setLocation(newPositions[i - 9]);
                //moveHandsPose[i].getLocation().y = model.bindPose[i].getLocation().y;
            }
        }

        /*


        moveHandsPose[17].setLocation(new vec3(newX, moveHandsPose[17].getLocation().y, newY));*/
    }

    public void setGesture(Gestures gest, vec3 point) {
        touchedPoint = point;
        switch(gest) {
            case ONE_HAND: this.gest = 1; break;
            case TWO_HANDS: this.gest = 2; break;
            case NO_GESTURES: this.gest = 0; break;
        }
    }

    public void resetMoveHandsPose() {
        for(int i=0;i<model.getnJoint();i++) {
            moveHandsPose[i] = new MD5Joint(model.bindPose[i]);
        }
    }

    public void turnOffLamp() {
        model.md5mesh[3].setVisible(false);
    }

    public void turnOnLamp() {
        model.md5mesh[3].setVisible(true);
    }

    public void setLocation(vec3 location) {
        this.location = location;
    }

    public vec3 getLocation() {
        return this.location;
    }

    public void setRotation(vec4 rotation) {
        this.rotation = rotation;
    }

    public void setScale(vec3 scale) {
        this.scale = scale;
    }

    public MD5 getModel() {
        return model;
    }

    public vec3 getOriginBoneLocation() {
        return model.bindPose[0].getLocation();
    }

    public void setModel(MD5 newModel) {
        model = newModel;
    }

    /**
     * Przesunięcie boba o wektor transform
     *
     * @param transform wektor przesunięcia
     */
    public void move(vec3 transform) {
        this.location.add(transform);
    }


    public void go(int pan) {
        if (physics) {
            box.setTransform(new Vec2(box.getPosition().x + pan / 30f, box.getPosition().y), 0f);
        }
    }

    public void jump() {
        if (physics && box.getLinearVelocity().y == 0 && gest == 0)
        {
            box.setLinearVelocity(new Vec2(0, 20f));
        }

    }

    public void applyPhysics() {
        if (physics) {
            this.setLocation(new vec3(box.getPosition().x, box.getPosition().y, this.getLocation().z));
        }
    }

    public Model_state getModelState() {
        return modelState;
    }

    public void setModelState(Model_state modelState) {
        this.modelState = modelState;
    }

    public void setSpeedRate (float speedRate) {
        this.speedRate = speedRate;
    }

    public void setMoveHandsPose(ArrayList<MD5JointState> moveHandsPose) {
        this.moveHandsPose = new MD5Joint[moveHandsPose.size()];
        for (int i = 0; i < moveHandsPose.size(); i++) {
            MD5JointState received = moveHandsPose.get(i);
            this.moveHandsPose[i] = new MD5Joint();
            this.moveHandsPose[i].setLocation(received.location);
            this.moveHandsPose[i].setRotation(received.rotation);
        }
    }

    //pobranie predkości w Y
    public float getVelocityY(){
        return box.getLinearVelocity().y;
    }

    public MD5Action getJumpAction(){
        return jumpAction;
    }

    public vec3 getTouchedPoint() {
        return touchedPoint;
    }

    public void setTouchedPoint(vec3 touchedPoint) {
        this.touchedPoint = touchedPoint;
    }

    public int getGest() {
        return gest;
    }

    public void setGest(int gest) {
        this.gest = gest;
    }
}
